package com.leilei.serviceProject.infrastructure.persistent.repository;


import com.leilei.serviceProject.domain.strategy.model.entity.StrategyAwardEntity;
import com.leilei.serviceProject.domain.strategy.model.entity.StrategyEntity;
import com.leilei.serviceProject.domain.strategy.model.entity.StrategyRuleEntity;
import com.leilei.serviceProject.domain.strategy.model.valobj.*;
import com.leilei.serviceProject.domain.strategy.repository.IStrategyRepository;
import com.leilei.serviceProject.infrastructure.persistent.dao.*;
import com.leilei.serviceProject.infrastructure.persistent.po.*;
import com.leilei.serviceProject.infrastructure.persistent.redis.IRedisService;
import com.leilei.serviceProject.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author yanglei
 * @description 策略服务仓储实现
 * @create 2024/10/16 9:18
 */
@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {
    @Resource
    private IStrategyAwardDao strategyAwardDao;
    @Resource
    private IStrategyDao strategyDao;
    @Resource
    private IStrategyRuleDao strategyRuleDao;
    @Resource
    private IRedisService redisService;
    @Resource
    private IRuleTreeDao ruleTreeDao;
    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;


    @Override
    public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
        // 1.优先从redis缓存中获取
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId;
        List<StrategyAwardEntity> strategyAwaryEntityList = redisService.getValue(cacheKey);
        if (null != strategyAwaryEntityList && !strategyAwaryEntityList.isEmpty()) {
            return strategyAwaryEntityList;
        }
        // 2.redis缓存中没取到从数据库中获取
        List<StrategyAward> strategyAwardList = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);
        strategyAwaryEntityList = new ArrayList<>(strategyAwardList.size());
        for (StrategyAward strategyAward : strategyAwardList) {
            StrategyAwardEntity strategyAwardEntity = StrategyAwardEntity.builder()
                    .strategyId(strategyAward.getStrategyId())
                    .awardId(strategyAward.getAwardId())
                    .awardCount(strategyAward.getAwardCount())
                    .awardCountSurplus(strategyAward.getAwardCountSurplus())
                    .awardRate(strategyAward.getAwardRate())
                    .build();
            strategyAwaryEntityList.add(strategyAwardEntity);
        }
        redisService.setValue(cacheKey, strategyAwaryEntityList);
        return strategyAwaryEntityList;
    }

    @Override
    public void restoreStrategyAwardRateSearchTable(String key, Integer shuffleStrategyAwardRateSearchTableSize, Map<Integer, Integer> shuffleStrategyAwardRateSearchTable) {
        // 1. 存储抽奖策略范围值，如10000，用于生成1000以内的随机数
        redisService.setValue(Constants.RedisKey.STRATEGY_AWARD_RANGE_KEY + key, shuffleStrategyAwardRateSearchTableSize);

        // 2.存储抽奖策略概率查找表
        /*
         * 当你通过 Redisson 获取一个分布式 `Map` 时，实际上得到的是一个代理对象，这个对象与 Redis 服务器上的数据结构是实时同步的。
         *  这个代理对象会拦截对 `Map` 的所有方法调用，并将这些调用转换为对 Redis 服务器的命令。
         *  当你对这个 `Map` 对象执行任何修改操作（如 `put`, `putAll`, `remove` 等）时，这些操作会自动同步到 Redis 服务器上
         */
        Map<Integer, Integer> strategyAwardRateMap = redisService.getMap(Constants.RedisKey.STRATEGY_AWARD_RATE_KEY + key);
        strategyAwardRateMap.putAll(shuffleStrategyAwardRateSearchTable);
    }

    @Override
    public int queryRateRange(Long strategyId) {
        return queryRateRange(String.valueOf(strategyId));
    }

    @Override
    public int queryRateRange(String key) {
        return redisService.getValue(Constants.RedisKey.STRATEGY_AWARD_RANGE_KEY + key);
    }

    @Override
    public Integer getStrategyAwardRateSearchTable(String strategyId, int rateKey) {
        return redisService.getFromMap(Constants.RedisKey.STRATEGY_AWARD_RATE_KEY + strategyId, rateKey);
    }

    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
        // 1.优先从缓存获取
        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey);
        if (null != strategyEntity) {
            return strategyEntity;
        }
        // 2. 从数据库获取
        Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
        strategyEntity = StrategyEntity.builder()
                .strategyId(strategy.getStrategyId())
                .strategyDesc(strategy.getStrategyDesc())
                .ruleModels(strategy.getRuleModels())
                .build();
        // 3.存储redis
        redisService.setValue(cacheKey, strategyEntity);
        return strategyEntity;
    }

    @Override
    public StrategyRuleEntity queryStrategyRuleEntity(Long strategyId, String ruleWeight) {
        // 1. 优先从缓存获取
        String cacheKey = Constants.RedisKey.STRATEGY_RULE_KEY + strategyId;
        StrategyRuleEntity strategyRuleEntity = redisService.getValue(cacheKey);
        if (null != strategyRuleEntity) {
            return strategyRuleEntity;
        }
        // 2.如果缓存中没有从数据库获取
        StrategyRule strategyRuleReq = new StrategyRule();
        strategyRuleReq.setStrategyId(strategyId);
        strategyRuleReq.setRuleModel(ruleWeight);
        StrategyRule strategyRuleRes = strategyRuleDao.queryStrategyRuleByStrategyIdAndRuleModel(strategyRuleReq);
        strategyRuleEntity = StrategyRuleEntity.builder()
                .strategyId(strategyRuleRes.getStrategyId())
                .ruleDesc(strategyRuleRes.getRuleDesc())
                .ruleType(strategyRuleRes.getRuleType())
                .ruleValue(strategyRuleRes.getRuleValue())
                .rulemodel(strategyRuleRes.getRuleModel())
                .build();
        // 3. 存入redis
        redisService.setValue(cacheKey, strategyRuleEntity);
        return strategyRuleEntity;
    }

    @Override
    public String queryStrategyRuleEntity(Long strategyId, Integer awardId, String ruleModel) {
        StrategyRule strategyRule = new StrategyRule();
        strategyRule.setStrategyId(strategyId);
        strategyRule.setAwardId(awardId);
        strategyRule.setRuleModel(ruleModel);

        return strategyRuleDao.queryStrategyRuleValue(strategyRule);
    }

    @Override
    public StrategyAwardRuleModelVo queryStrategyAwardRuleModelsEntity(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);

        String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
        return StrategyAwardRuleModelVo.builder()
                .ruleModels(ruleModels)
                .build();
    }

    @Override
    public RuleTreeVo queryRuleTreeVoByTreeId(String treeId) {
        // 1.优先从缓存获取
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
        RuleTreeVo ruleTreeVo = redisService.getValue(cacheKey);
        if (null != ruleTreeVo) {
            return ruleTreeVo;
        }
        // 2.从数据库获取
        RuleTree tree = ruleTreeDao.queryRuleTreeByTreeId(treeId);
        List<RuleTreeNode> treeNodes = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
        List<RuleTreeNodeLine> treeNodeLines = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);
        // 2.1.装载RuleTreeLineVo为Map<String, List<RuleTreeLineVo>>格式，RuleTreeNode用到RuleTreeNodeLine的时候直接从Map中获取
        Map<String, List<RuleTreeNodeLineVo>> treeNodeLineVoMap = new HashMap<>();
        for (RuleTreeNodeLine treeNodeLine : treeNodeLines) {
            RuleTreeNodeLineVo treeNodeLineVo = RuleTreeNodeLineVo.builder()
                    .treeId(treeNodeLine.getTreeId())
                    .fromTreeNode(treeNodeLine.getRuleNodeFrom())
                    .toTreeNode(treeNodeLine.getRuleNodeTo())
                    .ruleLimitType(RuleTreeLimitTypeVo.valueOf(treeNodeLine.getRuleLimitType()))
                    .ruleLimitValue(RaffleResultCheckVo.valueOf(treeNodeLine.getRuleLimitValue()))
                    .build();
            List<RuleTreeNodeLineVo> ruleTreeNodeLineVoList = treeNodeLineVoMap.computeIfAbsent(treeNodeLineVo.getFromTreeNode(), k -> new ArrayList<>());
            ruleTreeNodeLineVoList.add(treeNodeLineVo);
        }
        // 2.2.装载RuleTreeNodeVo为Map<String, RuleTreeNodeVo>，构建RuleTreeVo的时候直接使用该Map
        Map<String, RuleTreeNodeVo> treeNodeVoMap = new HashMap<>();
        for (RuleTreeNode treeNode : treeNodes) {
            RuleTreeNodeVo treeNodeVo = RuleTreeNodeVo.builder()
                    .treeId(treeNode.getTreeId())
                    .ruleKey(treeNode.getRuleKey())
                    .ruleValue(treeNode.getRuleValue())
                    .treeNodeLinkList(treeNodeLineVoMap.get(treeNode.getRuleKey()))
                    .build();
            treeNodeVoMap.put(treeNodeVo.getRuleKey(), treeNodeVo);
        }
        // 2.3.构建RuleTree
        ruleTreeVo = RuleTreeVo.builder()
                .treeId(tree.getTreeId())
                .treeName(tree.getTreeName())
                .treeDesc(tree.getTreeDesc())
                .rootNode(tree.getTreeNodeRuleKey())
                .treeNodeMap(treeNodeVoMap)
                .build();

        // 3.更新缓存
        redisService.setValue(cacheKey, ruleTreeVo);

        return ruleTreeVo;
    }

    @Override
    public Boolean awardStockDeduction(String cacheKey) {
        long surplus = redisService.decr(cacheKey);
        if (surplus < 0) {
            // 扣减后库存小于0，恢复为0个
            redisService.setValue(cacheKey, 0);
            return false;
        }
        // 1. 按照cacheKey decr 后的值，如 99、98、97 和 key 组成为库存锁的key进行使用。
        // 2. 加锁为了兜底，如果后续有恢复库存，手动处理等，也不会超卖。因为所有的可用库存key，都被加锁了。
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        Boolean lock = redisService.setNx(lockKey);
        if (!lock) {
            log.info("策略奖品库存加锁失败 {}", lockKey);
        }
        return lock;
    }

    @Override
    public void awardStockConsumeSendQueue(StrategyAwardCountSurplusVo strategyAwardCountSurplusVo) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUEUE_KEY;
        // 阻塞队列
        RBlockingQueue<StrategyAwardCountSurplusVo> blockingQueue = redisService.getBlockingQueue(cacheKey);
        // 延迟队列
        RDelayedQueue<StrategyAwardCountSurplusVo> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        // 像延迟队列推数
        delayedQueue.offer(strategyAwardCountSurplusVo,3, TimeUnit.SECONDS);
    }

    @Override
    public StrategyAwardCountSurplusVo takeQueueValue() throws InterruptedException{
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUEUE_KEY;
        RBlockingQueue<StrategyAwardCountSurplusVo> blockingQueue = redisService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();
    }

    @Override
    public void updateStrategyAwardStock(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);
        strategyAwardDao.updateStrategyAwardStock(strategyAward);
    }

    @Override
    public void cacheStrategyAwardCount(String cacheKey, Integer awardCount) {
        if (redisService.isExists(cacheKey)) return;
        redisService.setAtomicLong(cacheKey, awardCount);
    }
}
